package org.jeecg.modules.device.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zhouwr.common.device.vo.DeviceFunctionParamVo;
import com.zhouwr.common.device.vo.InstanceFunctionInputParam;
import com.zhouwr.common.device.vo.ModelFunctionStructure;
import com.zhouwr.common.enums.FunctionParamDirection;
import com.zhouwr.common.enums.FunctionParamInputMode;
import com.zhouwr.common.exception.IotDeviceOfflineException;
import com.zhouwr.common.message.SendMessage;
import com.zhouwr.common.metadata.ArrayData;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.modules.device.entity.*;
import org.jeecg.modules.device.enums.DeviceInstanceState;
import org.jeecg.modules.device.enums.DeviceType;
import org.jeecg.modules.device.listener.event.InstanceOfflineEvent;
import org.jeecg.modules.device.mapper.DeviceDataMapper;
import org.jeecg.modules.device.mapper.DeviceFunctionMapper;
import org.jeecg.modules.device.mapper.DeviceInstanceMapper;
import org.jeecg.modules.device.service.IDeviceFunctionParamService;
import org.jeecg.modules.device.service.IDeviceFunctionService;
import org.jeecg.modules.network.network.NetworkConnect;
import org.jeecg.modules.network.network.NetworkConnectStore;
import org.jeecg.modules.network.network.tcp.TcpSyncRequestUtil;
import org.jeecg.utils.HexConvertUtil;
import org.quartz.Scheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @Description: 功能定义
 * @Author: jeecg-boot
 * @Date: 2020-04-08
 * @Version: V1.0
 */
@Slf4j
@Service
public class DeviceFunctionServiceImpl extends ServiceImpl<DeviceFunctionMapper, DeviceFunction> implements IDeviceFunctionService {

    @Autowired
    private IDeviceFunctionService functionService;
    @Autowired
    private IDeviceFunctionParamService functionParamService;
    @Autowired
    private DeviceInstanceMapper deviceInstanceMapper;
    @Autowired
    private DeviceDataMapper deviceDataMapper;
    @Autowired
    private Scheduler scheduler;
    @Autowired
    private ApplicationEventPublisher publisher;

    @Override
    public List<DeviceFunction> selectByMainId(String mainId) {
        log.info(">>>>>{}", mainId);
        return functionService.lambdaQuery().eq(DeviceFunction::getDeviceModelBy, mainId).list();
    }

    @Override
    public List<FunctionInputParamVo> listInputParamByFuncId(String funcId) {
        return functionParamService.lambdaQuery()
                .eq(DeviceFunctionParam::getFunctionId, funcId)
                .eq(DeviceFunctionParam::getDirection, FunctionParamDirection.INPUT.getCode())
                .list()
                .stream().map(FunctionInputParamVo::new)
                .collect(Collectors.toList());
    }

    @Override
    public List<InputData> listInputDataByFuncId(String funcId, FunctionParamInputMode inputMode) {
        return listInputDataByFuncId(funcId).stream()
                .filter(inputData -> inputData.getInputMode().equals(inputMode))
                .collect(Collectors.toList());
    }

    @Override
    public List<InputData> listInputDataByFuncId(String funcId) {
        List<FunctionInputParamVo> inputParamList = listInputParamByFuncId(funcId);
        List<InputData> inputDataList = new ArrayList<>();
        for (FunctionInputParamVo inputParam : inputParamList) {
            InputData inputData = new InputData();
            DeviceData deviceData = deviceDataMapper.selectById(inputParam.getDataId());
            inputData.setDeviceData(deviceData);
            inputData.setInputMode(inputParam.getInputMode());
            inputDataList.add(inputData);
        }
        return inputDataList;
    }

    @Override
    public List<FunctionInputParamVo> listInputParam() {
        return null;
    }

    @Override
    public List<InputData> listInputData() {
        return null;
    }

    @Override
    public Boolean functionParamCheckUnique(ModelFunctionStructure functionStructure) {
        // 1、校验功能code
        final Integer functionCount = this.lambdaQuery().eq(DeviceFunction::getCode, functionStructure.getFunction().getCode()).count();
        if (functionCount > 0) {
            return false;
        }
        // 2、校验输入参数
        final int size = functionStructure.getInputParams()
                .stream()
                .collect(Collectors.groupingBy(DeviceFunctionParamVo::getDataId))
                .size();
        return size == functionStructure.getInputParams().size();
    }

    /**
     * 异步执行功能
     *
     * @param sendMessage
     */
    @Override
    public Boolean invokeFunctionAsync(SendMessage sendMessage) {
        ChannelHandlerContext ctx = getNetworkChannel(sendMessage.getInstanceId());
        // 发送数据
        ChannelFuture future = null;
        try {
            future = ctx.channel().writeAndFlush(sendMessage).await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return future.isDone();
    }

    @Override
    public Object invokeFunctionSync(SendMessage sendMessage) {
        log.info(" >>>>> 同步执行 <<<<<");
        ChannelHandlerContext ctx = getNetworkChannel(sendMessage.getInstanceId());
        Object bytes = TcpSyncRequestUtil.get(ctx, CommonConstant.SYNC_RECEIVE, sendMessage, 1000 * 2);
        log.info(" >>>>> 同步执行结果 >>>>> {}", bytes);
        return bytes;
    }

    /**
     * 生成发送命令
     *
     * @param inputParams
     * @return
     */
    @Override
    public Object generateSendCmds(List<InstanceFunctionInputParam> inputParams) {
        if (inputParams.size() == 1) {
            return generateSendCmd(inputParams.get(0));
        } else {
            return inputParams.stream().map(this::generateSendCmd);
        }
    }


    /**
     * 生成发送命令
     * 功能发送请求时，参数有多个时，包装成JSON对象
     *
     * @param inputParam
     * @return
     */
    @Override
    public Object generateSendCmd(InstanceFunctionInputParam inputParam) {

        final String type = inputParam.getType();
        log.info("数据类型: {}", type);
        switch (type) {
            case "array":
                final ArrayData array = (ArrayData) inputParam.getMetadata();
                final String split = array.getArraySplit();
                final String arrayType = array.getArrayType();
                log.info("数组项类型: {}", arrayType);
                return HexConvertUtil.hexStringToBytes(inputParam.getValue().toString());
//                return Arrays.stream(inputParam.getValue().toString().split(split))
//                        .filter(Objects::nonNull)
//                        .peek(System.out::println)
//                        .map(s -> {
//                            switch (arrayType) {
//                                case "int":
//                                    return Integer.valueOf(s);
//                                case "float":
//                                    return Float.valueOf(s);
//                                case "double":
//                                    return Double.valueOf(s);
//                                case "byte":
//                                    return HexConvertUtil.hexStringToBytes(s)[0];
//                                case "boolean":
//                                    return "true,1,y,yes".contains(s.toLowerCase());
//                                default:
//                                    return s;
//                            }
//                        }).toArray(Byte[]::new);
            case "int":
                return Integer.valueOf(inputParam.getValue().toString());
            case "float":
                return Float.valueOf(inputParam.getValue().toString());
            case "double":
                return Double.valueOf(inputParam.getValue().toString());
            case "byte":
                return HexConvertUtil.hexStringToBytes(inputParam.getValue().toString())[0];
            case "boolean":
                return "true,1,y,yes".contains(inputParam.getValue().toString().toLowerCase());
            case "string":
                return inputParam.getValue().toString();
            case "enum":
                return null;
            default:
                return null;
        }
    }

    /**
     * 根据设备实例id，获取网络连接通道
     *
     * @param instanceId
     * @return
     */
    public ChannelHandlerContext getNetworkChannel(String instanceId) {
        DeviceInstance instance = getParentGateway(instanceId);
        if (DeviceInstanceState.NOT_ACTIVE.equals(instance.getStatus())) {
            throw new RuntimeException("网关设备实例：" + instance.getName() + "[" + instance.getId() + "]" + "尚未激活！");
        } else if (DeviceInstanceState.OFFLINE.equals(instance.getStatus())) {
            throw new RuntimeException("网关设备实例：" + instance.getName() + "[" + instance.getId() + "]" + "离线，暂停执行！");
        }
        Map<String, NetworkConnect> connectMap = NetworkConnectStore.getNetworkConnectMap();
        Set<String> addrSet = connectMap.keySet();
        for (String addr : addrSet) {
            List<DeviceInstance> deviceInstanceList = deviceInstanceMapper.listOnlineInstanceByAddress(addr);
            if (deviceInstanceList != null && deviceInstanceList.size() > 0) {
                int count = deviceInstanceList.size();
                if (count > 1) {
                    throw new RuntimeException("网关设备实例：" + instance.getName() + "[" + instance.getId() + "]" + "服务器地址：" + addr + "配置冲突，请修改服务器地址唯一！");
                } else {
                    if (instance.getId().equals(deviceInstanceList.get(0).getId())) {
                        return connectMap.get(addr).getChannelHandlerContext();
                    }
                }
            }
        }
        // 找不到：强制下线
        publisher.publishEvent(new InstanceOfflineEvent(instance.getSceneBy(), instance.getId()));
        throw new IotDeviceOfflineException("没有获取连接通道，设备:" + instance.getName() + "强制下线，终止执行功能: ");
    }

    /**
     * 获取设备实例的父级网关设备实例
     *
     * @return
     */
    public DeviceInstance getParentGateway(String instanceId) {
        DeviceInstance instance = deviceInstanceMapper.selectById(instanceId);
        log.debug(">>> {}, {}", instanceId, instance);
        DeviceModel model = deviceInstanceMapper.getDeviceModelByModelId(instance.getModelBy());
        if (DeviceType.GATEWAY.equals(model.getType())) {
            return instance;
        } else {
            if (StringUtils.isEmpty(instance.getParentBy())) {
                throw new RuntimeException("找不到父级网关设备，请联系管理员！");
            }
            return getParentGateway(instance.getParentBy());
        }
    }

    /**
     * 根据功能Id，获取模型功能结构
     *
     * @param functionId
     * @return
     */
    @Override
    public ModelFunctionStructure buildFunctionStructure(String functionId) {
        final DeviceFunction function = this.getById(functionId);
        return new ModelFunctionStructure(
                function.vo(),
                functionParamService.getFunctionParams(functionId)
                        .stream()
                        .peek(System.out::println)
                        .map(DeviceFunctionParam::vo)
                        .collect(Collectors.toList())
        );
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void saveWithParams(ModelFunctionStructure functionStructure) {
        final DeviceFunction function = new DeviceFunction(functionStructure.getFunction());
        this.save(function);
        functionStructure.getInputParams().forEach(inputParamVo -> {
            inputParamVo.setFunctionId(function.getId());
            inputParamVo.setDirection(FunctionParamDirection.INPUT);
            functionParamService.save(new DeviceFunctionParam(inputParamVo));
        });
        DeviceFunctionParamVo outputParamVo = functionStructure.getOutputParam();
        if (outputParamVo != null) {
            outputParamVo.setFunctionId(functionStructure.getFunction().getId());
            outputParamVo.setDirection(FunctionParamDirection.OUTPUT);
            functionParamService.save(new DeviceFunctionParam(outputParamVo));
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void saveOrUpdateWithParams(ModelFunctionStructure functionStructure) {
        DeviceFunction function = new DeviceFunction(functionStructure.getFunction());
        this.saveOrUpdate(function);

        // 先删除，后添加
        functionParamService.removeByIds(
                functionParamService.getFunctionParams(functionStructure.getFunction().getId())
                        .stream()
                        .map(DeviceFunctionParam::getId)
                        .collect(Collectors.toList())
        );

        functionStructure.getInputParams().forEach(inputParamVo -> {
            inputParamVo.setFunctionId(function.getId());
            inputParamVo.setDirection(FunctionParamDirection.INPUT);
            functionParamService.save(new DeviceFunctionParam(inputParamVo));
        });

        DeviceFunctionParamVo outputParamVo = functionStructure.getOutputParam();
        if (outputParamVo != null) {
            outputParamVo.setDirection(FunctionParamDirection.OUTPUT);
            functionParamService.save(new DeviceFunctionParam(outputParamVo));
        }
    }
}
